using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace jsonReader
{
    public class JsonParser
    {
        private int currentColumn = 0;
        private int currentRow = 1;
        private StringReader jsonText;
        private IJsonValue jsonGraph;
        private int numberOfArrays = 0;
        private int numberOfObjects = 0;

        public IJsonValue Parse(string json) {
            if (String.IsNullOrEmpty(json)) {
                throw new ArgumentNullException("json");
            }

            jsonText = new StringReader(json);

            // Skip whitespaces
            EatWhiteSpace();

            if (EOS()) {
                throw new JsonParserException("EOF was unexpected.");
            }

            if ('{' == Peek()) {
                return ParseJsonObject();
            }

            return ParseJsonArray();
        }

        private IJsonValue ParseJsonArray() {
            return ParseJsonArray(string.Format("Array{0}", numberOfArrays++));
        }

        private IJsonValue ParseJsonArray(string name) {
            // Skip whitespaces
            EatWhiteSpace();

            // read away the [
            Read();

            // Skip whitespaces
            EatWhiteSpace();

            // parse the members
            JsonArray retVal = new JsonArray(name, ParseValueList());

            // Skip whitespaces
            EatWhiteSpace();

            // read away the }
            Read();

            return retVal;
        }

        private IJsonValue ParseJsonObject() {
            return ParseJsonObject(string.Format("Object{0}", numberOfObjects++));
        }

        private IJsonValue ParseJsonObject(string name) {
            // Skip whitespaces
            EatWhiteSpace();

            // read away the {
            Read();

            // Skip whitespaces
            EatWhiteSpace();

            // parse the members
            JsonObject retVal = new JsonObject(name, ParseMembers());

            // Skip whitespaces
            EatWhiteSpace();

            // read away the }
            Read();

            return retVal;
        }

        private JsonElementCollection ParseValueList() {
            JsonElementCollection retVal = new JsonElementCollection();

            while (']' != Peek()) {
                // Create new element
                JsonElement element = new JsonElement(); 
                
                // Skip whitespaces
                EatWhiteSpace();

                retVal.Add(ParseValue());

                // Skip whitespaces
                EatWhiteSpace();

                if (',' == Peek()) {
                    Read();
                }
                else {
                    if (']' != Peek()) {
                        throw new JsonParserException("Expected ,");
                    }
                }

                // Skip whitespaces
                EatWhiteSpace();
            }

            return retVal;
        }

        private JsonPairCollection ParseMembers() {
            JsonPairCollection retVal = new JsonPairCollection();

            while ('}' != Peek()) {
                // Skip whitespaces
                EatWhiteSpace();

                retVal.Add(ParseMember());

                // Skip whitespaces
                EatWhiteSpace();

                if (',' == Peek()) {
                    Read();
                }
                else {
                    if ('}' != Peek()) {
                        throw new JsonParserException("Expected ,");
                    }
                }

                // Skip whitespaces
                EatWhiteSpace();
            }

            return retVal;
        }

        private JsonPair ParseMember() {
            JsonPair retVal = new JsonPair();

            if ('"' == Peek()) {
                retVal.Name = ParseString();

                // Skip whitespaces
                EatWhiteSpace();

                if (':' != Read()) {
                    throw new JsonParserException("Unexpected token. Expected \":\".");
                }

                // Skip whitespaces
                EatWhiteSpace();

                retVal.Value = ParseValue();

                // Skip whitespaces
                EatWhiteSpace();
            }

            return retVal;
        }

        private IJsonValue ParseValue() {
            if ('"' == Peek()) {
                return ParseString();
            }
            else if ('t' == Peek()) {
                return ParseTrue();
            }
            else if ('f' == Peek()) {
                return ParseFalse();
            }
            else if ('n' == Peek()) {
                return ParseNull();
            }
            else if ('[' == Peek()) {
                return ParseJsonArray();
            }
            else if ('{' == Peek()) {
                return ParseJsonObject();
            }
            else if (char.IsDigit(Peek()) || '-' == Peek()) {
                return ParseNumber();
            }
            else {
                throw new JsonParserException("Unexcepted token");
            }
        }

        private IJsonValue ParseNumber() {
            // Skip whitespaces
            EatWhiteSpace();

            if (!char.IsDigit(Peek()) && '-' != Peek()) {
                throw new JsonParserException("Expected a number");
            }

            StringBuilder sb = new StringBuilder();
            if ('-' == Peek()) {
                sb.Append(Read());
            }

            if (!Char.IsDigit(Peek())) {
                throw new JsonParserException("Expected a digit");
            }
            
            if ('0' == Peek()) {
                sb.Append(Read());

                if ('.' == Peek()) {
                    sb.Append(Read());
                }
                else {
                    throw new JsonParserException("Expected '.'");
                }
            }

            while (char.IsDigit(Peek())) {
                sb.Append(Read());
            }

            if ('.' == Peek()) {
                sb.Append(Read());

                while (char.IsDigit(Peek())) {
                    sb.Append(Read());
                }
            }

            if ('e' == char.ToLower(Peek())) {
                sb.Append(Read());

                if (('+' == Peek()) || ('-' == Peek())) {
                    sb.Append(Read());
                }

                while (char.IsDigit(Peek())) {
                    sb.Append(Read());
                }
            }

            // Skip whitespaces
            EatWhiteSpace();

            return new JsonNumber(sb.ToString());
        }

        private string ParseIdentifier() {
            // Skip whitespaces
            EatWhiteSpace();

            if (!char.IsLetter(Peek())) {
                throw new JsonParserException("Expected an identifier");
            }

            StringBuilder sb = new StringBuilder();
            char ch;
            while (char.IsLetterOrDigit(Peek())) {
                ch = Read();
                sb.Append(ch);
            }

            // Skip whitespaces
            EatWhiteSpace();

            return sb.ToString();
        }

        private IJsonValue ParseNull() {
            string identifier = ParseIdentifier();

            if (!identifier.Equals("null")) {
                throw new JsonParserException("Expected 'null'");
            }

            return new JsonNull();
        }

        private IJsonValue ParseFalse() {
            string identifier = ParseIdentifier();

            if (!identifier.Equals("false")) {
                throw new JsonParserException("Expected 'false'");
            }

            return new JsonFalse();
        }

        private IJsonValue ParseTrue() {
            string identifier = ParseIdentifier();

            if (!identifier.Equals("true")) {
                throw new JsonParserException("Expected 'true'");
            }

            return new JsonTrue();
        }
        
        private JsonString ParseString() {
            JsonString retVal = new JsonString();

            // Skip whitespaces
            EatWhiteSpace();

            if ('"' != Read()) {
                throw new JsonParserException("Begin of string expected.");
            }

            StringBuilder sb = new StringBuilder();
            char ch;
            while ('"' != (ch = Read())) {
                sb.Append(ch);
            }

            retVal.Value = sb.ToString();

            // Skip whitespaces
            EatWhiteSpace();

            return retVal;
        }

        private char Peek() {
            return (char)jsonText.Peek();
        }

        private char Read() {
            currentColumn++;
            return (char)jsonText.Read();
        }

        private void EatWhiteSpace() {
            while (!EOS() && Char.IsWhiteSpace(Peek())) {
                if ('\n' == Read()) {
                    currentRow++;
                    currentColumn = 0;
                }
            }
        }

        private bool EOS() {
            return (-1 == jsonText.Peek());
        }
    }
}
